//-----------------------------------------------------------------------------
//  Copyright (C) 2011-2018, GB Research, LLC (www.gbresearch.com)
//  
//  Boost Software License - Version 1.0 - August 17th, 2003
//
//  Permission is hereby granted, free of charge, to any person or organization
//  obtaining a copy of the software and accompanying documentation covered by
//  this license (the "Software") to use, reproduce, display, distribute,
//  execute, and transmit the Software, and to prepare derivative works of the
//  Software, and to permit third-parties to whom the Software is furnished to
//  do so, all subject to the following:
//
//  The copyright notices in the Software and this entire statement, including
//  the above license grant, this restriction and the following disclaimer,
//  must be included in all copies of the Software, in whole or in part, and
//  all derivative works of the Software, unless such copies or derivative
//  works are solely in the form of machine-executable object code generated by
//  a source language processor.
//
//  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
//  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
//  FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
//  SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
//  FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
//  ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
//  DEALINGS IN THE SOFTWARE.
//-----------------------------------------------------------------------------

#pragma once

#include <array>
#include <iterator>
#include <string>
#include <sstream>
#include <math.h>
#include <utility>
#include <stddef.h>
#include <regex>

#include "axe_trait.h"
#include "axe_result.h"
#include "axe_predicate.h"
#include "axe_composite.h"
#include "axe_iterator.h"

namespace axe
{

    //-------------------------------------------------------------------------
    /// r_bool rule allows including boolean expression evaluated at parse time
    /// a (lambda) function taking 0,1, or 2 arguments and returing boolean 
    /// or a boolean variable can be passed to the constructor and will be invoked at parse time
    /// argument is held by copy, which is evaluated at construction time
    /// to make it evaluated at parse time wrap a variable in std::ref
    /// this rule doesn't advance the iterators
    //-------------------------------------------------------------------------
    template<class BoolT>
    class r_bool final
    {
        BoolT b_; // (lambda) function returning bool or boolean variable
    public:
        template<class T, class = detail::disable_copy<r_bool<BoolT>, T>>
        explicit r_bool(T&& t) : b_(std::forward<T>(t)) {}

        template<class Iterator, class Iterator2>
        result<Iterator> operator() (Iterator i1, Iterator2 i2) const
        {
            static_assert(is_input_iterator<Iterator>);
            static_assert(std::is_convertible_v<std::decay_t<BoolT>, bool>
                || is_function_object_v<BoolT, bool>
                || is_function_object_v<BoolT, bool, Iterator>
                || is_function_object_v<BoolT, bool, Iterator, Iterator>);
            
            if (i1 == i2)
                return make_result(false, i1);

            if constexpr(std::is_convertible_v<std::decay_t<BoolT>, bool>)
                return make_result(b_, i1);
            else if constexpr(is_function_object_v<BoolT, bool>)
                return make_result(std::invoke(b_), i1);
            else if constexpr(is_function_object_v<BoolT, bool, Iterator>)
                return make_result(std::invoke(b_, i1), i1);
            else if constexpr(is_function_object_v<BoolT, bool, Iterator, Iterator>)
                return make_result(std::invoke(b_, i1, i2), i1);
        }

        const char* name() const { return "r_bool"; }
    };

    template<class BoolT>
    explicit r_bool(BoolT&& b) ->r_bool<std::decay_t<BoolT>>;

    //-------------------------------------------------------------------------
    /// Empty rule always return match and doesn't advance iterator
    /// it is used in the composite rules that require a rule that does nothing
    //-------------------------------------------------------------------------
    struct r_empty final
    {
        template<class Iterator>
        result<Iterator> operator() (Iterator i1, Iterator) const
        {
            static_assert(is_input_iterator<Iterator>);
            return result(true, i1);
        }

        const char* name() const { return "r_empty"; }
    };
    
    //-------------------------------------------------------------------------
    /// rule to perform single character match
    //-------------------------------------------------------------------------
    template<class CharT>
    class r_char final
    {
        static_assert(!AXE_IS_RULE(CharT));
        CharT t;
    public:
        template<class U, class = detail::disable_copy<r_char<CharT>, U>>
        explicit r_char(U&& u) : t(std::forward<U>(u)) {}

        template<class Iterator, class Iterator2>
        result<Iterator> operator() (Iterator i1, Iterator2 i2) const
        {
            static_assert(is_input_iterator<Iterator>);
            static_assert(is_eq_comparable<CharT, decltype(*i1)>);
            return i1 != i2 && t == *i1 ? result(true, std::next(i1)) : result(false, i1);
        }

        const char* name() const { return "r_char"; }
    };

    template<typename T>
    explicit r_char(T&&)-> r_char<std::decay_t<T>>;
        
    //-------------------------------------------------------------------------
    /// binary rule: match byte by byte standard layout types
    //-------------------------------------------------------------------------
    template<class T>
    class r_bin final
    {
        static_assert(!AXE_IS_RULE(T));
        static_assert(std::is_standard_layout_v<T>);
        T t;

    public:
        template<class U, class = detail::disable_copy<r_bin<T>, U>>
        explicit r_bin(U&& u) : t(std::forward<U>(u)) {}

        template<class Iterator, class Iterator2>
        result<Iterator> operator() (Iterator i1, Iterator2 i2) const
        {
            static_assert(is_input_iterator<Iterator>);
            static_assert(sizeof(*i1) == 1); // byte match

            auto* p = reinterpret_cast<const unsigned char*>(&t);
            size_t s = 0;
            // compare each byte
            for(; i1 != i2 && s < sizeof(T) && *p == *i1; ++i1, ++p, ++s);
            return make_result(s == sizeof(T), i1);
        }

        const char* name() const { return "r_bin"; }
    };

    template<typename T>
    explicit r_bin(T&&)->r_bin<std::decay_t<T>>;
    
    //-------------------------------------------------------------------------
    /// rule to perform value match for string literals
    //-------------------------------------------------------------------------
    template<auto C1, auto... C>
    struct r_strlit
    {
        template<class Iterator, class Iterator2>
        result<Iterator> operator() (Iterator i1, Iterator2 i2) const
        {
            if (i1 != i2 && C1 == *i1)
            {
                if constexpr (sizeof...(C) != 0)
                {
                    return r_strlit<C...>{}(std::next(i1), i2);
                }
                return result(true, std::next(i1));
            }
            return result(false, i1);
        }

        const char* name() const { return "r_strlit"; }
    };

    //-------------------------------------------------------------------------
    /// rule to perform value match for 0 terminated strings
    //-------------------------------------------------------------------------
    template<class CharT>
    class r_str final
    {
        static_assert(!AXE_IS_RULE(CharT));
        const CharT* str_;

    public:
        explicit r_str(const CharT* str) : str_(str) {}

        template<class Iterator, class Iterator2>
        result<Iterator> operator() (Iterator i1, Iterator2 i2) const
        {
            static_assert(is_input_iterator<Iterator>);
            static_assert(std::is_convertible<decltype(*i1), CharT>::value);

            // empty string always match
            if(!str_ || !str_[0])
                return make_result(true, i1);

            size_t s = 0;
            for(; i1 != i2 && str_[s] && str_[s] == *i1; ++s, ++i1);
            return make_result(!str_[s], i1);
        }
        
        const CharT* name() const { return str_; }
    };

    template<class CharT>
    explicit r_str(const CharT*)->r_str<CharT>;

    //-------------------------------------------------------------------------
    /// rule to perform value match for std::string rvalues
    //-------------------------------------------------------------------------
    template<class CharT, class TraitsT, class AllocT>
    class r_str<std::basic_string<CharT, TraitsT, AllocT>> final
    {
        using StringT = std::basic_string<CharT, TraitsT, AllocT>;
        StringT str_;

    public:
        template<class U, class = detail::disable_copy<r_str<StringT>, U>>
        explicit r_str(U&& u) : str_(std::forward<U>(u)) {}

        template<class Iterator, class Iterator2>
        result<Iterator> operator() (Iterator i1, Iterator2 i2) const
        {
            static_assert(is_input_iterator<Iterator>);
            static_assert(std::is_convertible<decltype(*i1), CharT>::value);
            auto i = str_.begin();
            auto str_end = str_.end();
            for(; i1 != i2 && i != str_end && *i == *i1; std::advance(i, 1), std::advance(i1, 1));
            return make_result(i == str_end, i1);
        }
        
        const StringT& name() const { return str_; }
    };

    template<class CharT, class TraitsT, class AllocT>
    explicit r_str(std::basic_string<CharT, TraitsT, AllocT>&&)->r_str<std::basic_string<CharT, TraitsT, AllocT>>;
    template<class CharT, class TraitsT, class AllocT>
    explicit r_str(const std::basic_string<CharT, TraitsT, AllocT>&)->r_str<std::basic_string<CharT, TraitsT, AllocT>>;

    //-------------------------------------------------------------------------
    /// rule matches a single element satisfying predicate
    //-------------------------------------------------------------------------
    template<class Pred>
    class r_pred final 
    {
        static_assert(AXE_IS_PREDICATE(Pred));
        Pred pred_;
    public:
        template<class U, class = detail::disable_copy<r_pred<Pred>, U>>
        r_pred(U&& pred) : pred_(std::forward<U>(pred)) {}

        template<class Iterator, class Iterator2>
        result<Iterator> operator() (Iterator i1, Iterator2 i2) const
        {
            static_assert(is_input_iterator<Iterator>);
            return i1 != i2 && pred_(*i1) ? result(true, std::next(i1)) : result(false, i1);
        }

        const char* name() const { return "r_pred"; }
    };

    template<class Pred>
    r_pred(Pred&&)->r_pred<std::decay_t<Pred>>;
    
    //-------------------------------------------------------------------------
    /// rule matches a string of elements satisfying predicate
    //-------------------------------------------------------------------------
    template<class Pred, bool = false>
    class r_predstr final 
    {
        static_assert(AXE_IS_PREDICATE(Pred));
        Pred pred_;
    public:
        template<class U, class = detail::disable_copy<r_pred<Pred>, U>>
        r_predstr(U&& pred) : pred_(std::forward<U>(pred)) {}

        template<class Iterator, class Iterator2>
        result<Iterator> operator() (Iterator i1, Iterator2 i2) const
        {
            static_assert(is_input_iterator<Iterator>);
            Iterator i = i1;
            for(; i != i2 && pred_(*i); i = std::next(i));
            return make_result(i != i1, i);
        }

        const char* name() const { return "r_predstr"; }
    };

    template<class Pred>
    r_predstr(Pred&&)->r_predstr<std::decay_t<Pred>>;

    //-------------------------------------------------------------------------
    /// rule matches a string of specified length of elements satisfying predicate
    //-------------------------------------------------------------------------
    template<class Pred>
    class r_predstr<Pred, true> final 
    {
        Pred pred_;
        size_t min_occurrence_;
        size_t max_occurrence_;
    public:
        template<class U>
        r_predstr(U&& pred, size_t occurrence)
            : pred_(std::forward<U>(pred)), min_occurrence_(occurrence), max_occurrence_(occurrence)
        {
        }

        template<class U>
        r_predstr(U&& pred, size_t min_occurrence, size_t max_occurrence)
            : pred_(std::forward<U>(pred)), min_occurrence_(min_occurrence), max_occurrence_(max_occurrence)
        {
        }

        template<class Iterator, class Iterator2>
        result<Iterator> operator() (Iterator i1, Iterator2 i2) const
        {
            static_assert(is_input_iterator<Iterator>);
            Iterator i = i1;
            size_t count = 0;
            for(; count < max_occurrence_ && i != i2 && pred_(*i); ++i, ++count);

            return make_result(count >= min_occurrence_, i);
        }

        std::string name() const
        {
            std::ostringstream ss("r_predstr(pred, ");
            ss << min_occurrence_ << ", " << max_occurrence_ << ')';
            return std::move(ss).str();
        }
    };

    template<class Pred>
    r_predstr(Pred&&, size_t)->r_predstr<std::decay_t<Pred>, true>;
    template<class Pred>
    r_predstr(Pred&&, size_t, size_t)->r_predstr<std::decay_t<Pred>, true>;

    //-------------------------------------------------------------------------
    /// r_var rule matches a variable of type T (binary) and reads its value
    //-------------------------------------------------------------------------
    template<class T>
    class r_var final 
    {
        static_assert(std::is_standard_layout_v<T> && std::is_trivially_constructible_v<T>);
        T& t;

    public:
        explicit r_var(T& t) : t(t) {}

        template<class Iterator, class Iterator2>
        result<Iterator> operator() (Iterator i1, Iterator2 i2) const
        {
            static_assert(is_input_iterator<Iterator>);
            static_assert(sizeof(*i1) == 1, "iterator must be byte size for binary match");

            unsigned char* c = reinterpret_cast<unsigned char*>(&t);
            unsigned s = 0;
            for(; s < sizeof(T) && i1 != i2; ++i1, ++s)
                c[s] = *i1;

            return make_result(s == sizeof(T), i1);
        }

        const char* name() const { return "r_var"; }
    };

    template<class T>
    explicit r_var(T&)->r_var<std::decay_t<T>>;

    //-------------------------------------------------------------------------
    /// r_array rule reads values to a static array
    //-------------------------------------------------------------------------
    template<class T, size_t N>
    class r_array final 
    {
        static_assert(std::is_standard_layout_v<T>);
        std::array<T, N>& buf_;

    public:
        explicit r_array(std::array<T, N>& a) : buf_(a) {}

        template<class Iterator, class Iterator2>
        result<Iterator> operator() (Iterator i1, Iterator2 i2) const
        {
            static_assert(is_forward_iterator<Iterator>);
            size_t s = 0;

            for(bool matched = true; matched && s < N && i1 != i2; ++s)
            {
                r_var<T> tmp(buf_[s]);

                auto&& result = tmp(i1, i2);

                i1 = result.position;
                matched = result.matched;
            }

            return make_result(s == N, i1);
        }

        const char* name() const { return "r_array"; }
    };

    template<class T, size_t N>
    explicit r_array(std::array<T, N>&)->r_array<std::decay_t<T>, N>;

    //-------------------------------------------------------------------------
    /// r_sequence rule reads sequence of specified length
    //-------------------------------------------------------------------------
    template<class C, typename = decltype(std::declval<C>().push_back(std::declval<typename C::value_type>()))>
    class r_sequence final 
    {
        using T = typename C::value_type;
        static_assert(std::is_standard_layout_v<T> && std::is_trivially_constructible_v<T>);
        C& buf_;
        const size_t min_occurrence_;
        const size_t max_occurrence_;

    public:
        //----------------------------
        r_sequence(C& buf, size_t min_occurrence, size_t max_occurrence)
            : buf_(buf), min_occurrence_(min_occurrence), max_occurrence_(max_occurrence)
        {
            buf_.clear();
        }

        //----------------------------
        template<class Iterator, class Iterator2>
        result<Iterator> operator() (Iterator i1, Iterator2 i2) const
        {
            static_assert(is_forward_iterator<Iterator>);
            size_t s = 0;

            for(bool matched = true; matched && i1 != i2 && s < max_occurrence_; ++s)
            {
                T t;
                auto&& r = r_var<T>(t)(i1, i2);
                i1 = r.position;
                matched = r.matched;
                if(matched)
                    buf_.push_back(std::move(t));
            }

            return make_result(buf_.size() >= min_occurrence_, i1);
        }

        const char* name() const { return "r_sequence"; }
    };

    template<class C>
    r_sequence(C&, size_t = 0, size_t = -1)->r_sequence<C>;

    //-------------------------------------------------------------------------
    /// r_ident rule matches identifier (letter followed by letters and digits)
    //-------------------------------------------------------------------------
    struct r_ident final 
    {
        template<class Iterator, class Iterator2>
        result<Iterator> operator() (Iterator i1, Iterator2 i2) const
        {
            static_assert(is_input_iterator<Iterator>);
            auto&& r = r_pred<is_alpha>(is_alpha())(i1, i2);
            if(r.matched)
            {   // all other alnum chars are optional
                auto r1 = r_predstr<is_alnum>(is_alnum())(r.position, i2);
                if(r1.matched)
                    r = r1;
            }

            return r;
        }

        const char* name() const { return "r_ident"; }
    };

    //-------------------------------------------------------------------------
    /// r_end matches end of parsing range
    //-------------------------------------------------------------------------
    struct r_end final 
    {
        template<class Iterator, class Iterator2>
        result<Iterator> operator() (Iterator i1, Iterator2 i2) const
        {
            return make_result(i1 == i2, i1);
        }

        const char* name() const { return "r_end"; }
    };

    //-------------------------------------------------------------------------
    /// r_advance succeeds when it can advance iterator by specified offset
    //-------------------------------------------------------------------------
    template<class OffsetT>
    class r_advance final 
    {
        OffsetT offset_;
    public:

        r_advance(OffsetT offset) : offset_(offset) {}

        template<class Iterator, class Iterator2>
        result<Iterator> operator() (Iterator i1, Iterator2 i2) const
        {
            static_assert(is_input_iterator<Iterator>);
            static_assert(std::is_convertible_v<OffsetT,
                typename std::iterator_traits<Iterator>::difference_type>);

            typename std::iterator_traits<Iterator>::difference_type i = 0;
            for (; i1 != i2 && i != offset_; ++i1, ++i);
            return result(i == offset_, i1);
        }

        const char* name() const { return "r_advance"; }
    };

    template<class OffsetT>
    r_advance(OffsetT)->r_advance<std::decay_t<OffsetT>>;
    
    //-------------------------------------------------------------------------
    /// r_range creates rule to match values from specified iterator range
    //-------------------------------------------------------------------------
    template<class Iterator>
    class r_range final 
    {
        Iterator begin, end;
    public:
        r_range(Iterator begin, Iterator end) : begin(begin), end(end) {}

        template<class Container, 
            class = std::void_t<decltype(std::begin(std::declval<Container>())),
            decltype(std::end(std::declval<Container>()))>>
        explicit r_range(const Container& c) : begin(std::begin(c)), end(std::end(c)) {}


        template<class I>
        result<I> operator() (I i1, I i2) const
        {
            static_assert(is_input_iterator<I>);
            auto i = begin;
            for(; i1 != i2 && i != end && *i1 == *i; ++i, ++i1);
            return result(i == end, i1);
        }

        const char* name() const { return "r_range"; }
    };

    template<class Iterator>
    r_range(Iterator, Iterator)->r_range<Iterator>;

    template<class Container>
    explicit r_range(const Container&)->r_range<decltype(std::begin(std::declval<Container>()))>;

    //-------------------------------------------------------------------------
    /// r_regex creates rule from regular expression
    //-------------------------------------------------------------------------
    template<class CharT, class Traits = std::regex_traits<CharT>>
    class r_regex final 
    {
        using Self = r_regex<CharT, Traits>;
        std::basic_regex<CharT, Traits> rx; // regex to match
    public:
        template<class Arg, class... Args, class = detail::disable_copy<Arg, Self>>
        explicit r_regex(Arg&& arg, Args&& ...args) : rx(std::forward<Arg>(arg), std::forward<Args>(args)...) {}

        template<class I>
        auto operator() (I i1, I i2) const
        {
            static_assert(is_bidirectional_iterator<I>);
            std::match_results<I> mr;
            auto matched = std::regex_search(i1, i2, mr, rx) && mr.prefix().length() == 0;
            return make_result(matched, mr.suffix().first, i1);

            return result(false, i1);
        }

        const char* name() const { return "r_regex"; }
    };

    template<class CharT>
    explicit r_regex(const CharT*)->r_regex<CharT>;

    template<class CharT, class ST, class SA>
    explicit r_regex(const std::basic_string<CharT, ST, SA>&)->r_regex<CharT>;

} // namespace
